Analytics rewrite: generic provider SPI with GDPR consent and multiple backends#5266
Open
shai-almog wants to merge 17 commits into
Open
Analytics rewrite: generic provider SPI with GDPR consent and multiple backends#5266shai-almog wants to merge 17 commits into
shai-almog wants to merge 17 commits into
Conversation
…e backends Replace the deprecated Google Analytics v1 AnalyticsService with a generic provider SPI. Apps register one or more AnalyticsProvider implementations with the Analytics facade, which fans screen / event / user-property / crash calls out to all providers after a configurable (opt-in by default) consent gate. Providers: CodenameOneAnalyticsProvider (first-party, batched to the cloud), GoogleAnalyticsProvider (GA4), MatomoAnalyticsProvider (privacy-first, non Google), FirebaseAnalyticsProvider (Android + iOS native peers), and LoggingAnalyticsProvider (simulator / tests). The old AnalyticsService is retained, deprecated, and now delegates to the new API. Adds GDPR features (granular consent persisted across restarts, pseudonymous user-resettable client id), Firebase build-hint dependency injection in the Android (android.firebaseAnalytics) and iOS (ios.firebaseAnalytics) builders, a new developer-guide Analytics chapter, and 25 unit tests. The iOS native peer was verified with a full local arm64 xcodebuild (BUILD SUCCEEDED). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Collaborator
Author
|
Compared 136 screenshots: 136 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
Collaborator
Author
|
Compared 128 screenshots: 128 matched. |
Collaborator
Author
|
Compared 134 screenshots: 134 matched. Benchmark Results
Detailed Performance Metrics
|
Contributor
Cloudflare Preview
|
Collaborator
Author
|
Compared 211 screenshots: 211 matched. |
Collaborator
Author
|
Compared 135 screenshots: 135 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Collaborator
Author
The CN1 core (CodenameOne/src) compiles against the CLDC11 bootclasspath, whose StringBuilder has no substring(int,int). Convert to String first in GoogleAnalyticsProvider.sanitizeName so the core/CLDC11 build compiles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The deprecated AnalyticsService now delegates to Analytics, leaving domain / timeout / readTimeout written but never read -- SpotBugs failed the JDK 8 gate on URF_UNREAD_FIELD. Remove the fields; the setters become documented no-ops and the init domain parameter is retained for source compatibility only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make Analytics final (ClassWithOnlyPrivateConstructorsShouldBeFinal) and drop the redundant public modifiers on NativeFirebaseAnalytics interface methods (UnnecessaryModifier). Verified against the full forbidden PMD rule set via mvn verify -- zero forbidden violations across the analytics sources. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
CI LanguageTool (rendered-HTML gate, not runnable locally) flagged British spellings 'behavioural'/'anonymisation' and the proper noun 'Piwik'. Switch to US spelling (behavioral/anonymization, plus honor) and accept-list Piwik. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f4a7d53 to
2422a15
Compare
The CodenameOneAnalyticsProvider batch now carries a per-run sessionId and device segmentation fields (deviceModel, deviceManufacturer, formFactor, density, screenWidth/Height, network, language) so the cloud console can do Google-Analytics-style segmentation, sessions and screen-flow without any hardware identifier. Values are read defensively; the model is the public hardware string, never the user-assigned device name. Ports expose a privacy-safe hardware model via a new DeviceHardwareModel property: iOS adds native getDeviceHardwareModel() (sysctlbyname hw.machine, with a simulator fallback) -- distinct from getDeviceName() which returns the PII device name; Android maps it to Build.MODEL and adds DeviceManufacturer (Build.MANUFACTURER). Adds provider tests for the enriched wire format and stable per-run session id. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nted Purchase/share
- Analytics.setDimension/clearDimension/getDimensions: app-defined custom
segmentation dimensions, persisted via Preferences and sent in the batch as
a dimensions{} object. Plus browser (coarse label from User-Agent on web)
and osName in the batch.
- Analytics.autoEvent(): consent-gated, no-op when no providers, never throws.
Purchase.postReceipt emits 'purchase'(commerce, sku); Display.share emits
'share'(engagement, type) -- goals can bind to these out of the box.
- Tests for dimensions round-trip/persistence, batch dimensions+browser, and
autoEvent fan-out/no-op.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # docs/developer-guide/languagetool-accept.txt
Analytics.getDimensions() always returns a fresh LinkedHashMap, never null, so the 'dimensions == null' branch in appendDimensions tripped the forbidden RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE SpotBugs rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When an app has already obtained consent through its own mechanism (custom prompt, third-party CMP, MDM policy, or a jurisdiction where the integrator deems consent unnecessary) it can unblock reporting in one line: Analytics.setConsent(AnalyticsConsent.all()). Adds all()/none() static factories (granted()/denied() kept as aliases) plus Builder.all()/none() so a single category can be selectively revoked, e.g. builder().all().adStorage(false). Consent gating stays opt-in by default; this just makes the established escape hatch obvious. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Developer guide consent section now leads with AnalyticsConsent.all(), shows builder().all().adStorage(false), adds a 'Consent you already hold' subsection covering app-managed consent and OPT_OUT, and explains that the data is pseudonymous (not anonymous) so opt-in is the safe default. Passes asciidoctor lint, paragraph-cap, Vale (0) and LanguageTool (0) locally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 'non-Google' - Analytics.crash javadoc + developer guide now spell out that it emits a lightweight exception event to analytics backends and is independent of Codename One Crash Protection (com.codename1.crash.CrashProtection), the dedicated symbolicated crash pipeline -- use either or both. - Developer guide intro now points readers to the Consent and privacy section (new [#analytics-consent] anchor) instead of a bare clause. - MatomoAnalyticsProvider javadoc drops the 'non-Google' wording. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NativeInterface exists for the build server to generate per-app native peers; that is the wrong fit for a framework-shipped provider. Replace it with FirebaseAnalyticsProvider.Bridge -- a plain interface the build registers via registerBridge() before startup. The provider is a safe no-op until a bridge is registered (simulator/desktop, or a build without Firebase). Removes the NativeFirebaseAnalytics interface, the reflection- based Android peer and the iOS native peer; the per-platform bridge implementations are now generated by the build (Android: direct SDK calls; iOS: native methods). Core + analytics unit tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When android.firebaseAnalytics / ios.firebaseAnalytics is set, the build now generates a FirebaseAnalyticsProvider.Bridge and registers it in the app Stub before init(): - Android: FirebaseAnalyticsBridgeImpl.java calling the FirebaseAnalytics SDK directly (no reflection), emitted next to the Stub. - iOS: FirebaseAnalyticsBridgeImpl.java with native methods + a generated cn1_firebase_analytics_bridge.m that invokes FIRAnalytics dynamically (so it links without the pod; no-op when absent). Replaces the removed NativeFirebaseAnalytics native interface. Maven plugin compiles; twin change in BuildDaemon #130. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Compared 133 screenshots: 133 matched. |
The Firebase config-file locations were old Ant paths (native/android, native/ios). Use the Maven module resource paths instead (android/src/main/resources/google-services.json, ios/src/main/resources/GoogleService-Info.plist), and spell out that both the config file and the build hint are required, that the build generates the native bridge, and that Firebase only reports from a native device build -- never the simulator or desktop run, where the provider no-ops. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
google-services.json and the push icon go in android/src/main/resources in a Maven project, not the old Ant native/android directory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Social Login fires login / login_failed (with the provider in 'service') from the central LoginCallBackProxy. - Purchase.purchase() is now a template method that fires purchase_initiated then calls a new protected purchaseImpl() (the platform ZoozPurchase impls override that). Pairs with the existing 'purchase' completion event so the console can show checkout drop-off. Non-breaking: the public purchase() signature is unchanged and custom subclasses still work. - Documents the automatic events (purchase_initiated, purchase, login, login_failed, share) in the developer guide. - Adds AnalyticsPurchaseFunnelTest (consent-gated; reaches purchaseImpl). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Replaces the deprecated Google Analytics v1
AnalyticsServicewith a generic provider SPI. Apps register one or moreAnalyticsProviderimplementations with theAnalyticsfacade, which fansscreen/event/setUserProperty/crashcalls out to all providers — but only after a configurable, opt-in-by-default consent gate.Client (
com.codename1.analytics)Analyticsfacade +AnalyticsProviderSPI (AbstractAnalyticsProviderbase),AnalyticsEvent,AnalyticsConsent,ConsentMode,AnalyticsContext,AnalyticsCrashReport,AnalyticsCapability.resetClientId()); no hardware identifiers.CodenameOneAnalyticsProvider(first-party, batched to the cloud),GoogleAnalyticsProvider(GA4 Measurement Protocol),MatomoAnalyticsProvider(privacy-first, non-Google),FirebaseAnalyticsProvider,LoggingAnalyticsProvider.AnalyticsServiceis retained, deprecated, and delegates to the new API (itsinitswitches the facade to opt-out to preserve historical behaviour).UIBuilderanalytics hook unchanged.Firebase native + builders
NativeFirebaseAnalyticsImpl) via reflection (mirrors the port's FCM pattern); iOS peer as an Objective-C class (com_codename1_analytics_NativeFirebaseAnalyticsImpl) using dynamic dispatch onFIRAnalytics.AndroidGradleBuilder/IPhoneBuilderinject the Firebase Gradle dep /Firebase/Analyticspod, gated on theandroid.firebaseAnalytics/ios.firebaseAnalyticsbuild hints (mirrors the existing FCM/AdMob handling).Docs + tests
docs/developer-guide/Analytics.asciidocchapter (old section replaced with a pointer); passes asciidoctor + Vale + paragraph-cap locally.maven/core-unittests(consent gating, all providers, client id, capabilities, deprecated facade) — all green against a from-source core build.Verification
mvn -o -pl core-unittests -DunitTests=true -Plocal-dev-javase test -Dtest='*Analytics*'→ 25/25 pass.xcodebuild -sdk iphoneos -arch arm64of a sample app → BUILD SUCCEEDED, peer object compiled and linked.🤖 Generated with Claude Code